#include <iostream>
#include <string>
#include <gmp.h>
#include <gmpxx.h>
#include <string>
#include <fstream>
#include <sstream>
#include <list>
#include <pthread.h>


#define BITS_PER_DIGIT 3.32192809488736234787

pthread_mutex_t ma; // Mutex de acesso - comeca com 1
std::list<mpf_class> la;

pthread_mutex_t my; // Mutex de acesso - comeca com 1
pthread_mutex_t req_am;
bool req_a;
std::list<mpf_class> ly;

pthread_mutex_t req_ym;
bool req_y;

std::list<mpf_class> lpi;

int liter = 12;

void * thread_y (void*) {
	mpf_class y(sqrt(mpf_class(2)) - 1);
	for (int i = 0; i < liter; i++) {
		y = y * y;
		y *= y;
		y = sqrt(sqrt(1 - y));
		y = (1 - y) / (1 + y);
		pthread_mutex_lock(&my);
		{
			ly.push_back(y);
			if (req_y) {
				req_y = false;
				pthread_mutex_unlock(&req_ym);
			}
		}
		pthread_mutex_unlock(&my);
	}
	return 0;
}

mpf_class * _y;
mpf_class * aP1;
mpf_class * aP2;

void * thread_aP1 (void *) {
	*aP1 *= *aP1 *= *aP1 = (1 + *_y);
	return 0;
}

void * thread_aP2 (void *) {
	*aP2 = *_y * (1 + *_y + *_y * *_y);
	return 0;
}

void * thread_a (void *) {
	mpf_class a(6 - 4 * sqrt(mpf_class(2)));
	mpf_class tmp;
	mpf_class pow2k3(8);
	_y = new mpf_class();
	aP1 = new mpf_class();
	aP2 = new mpf_class();
	pthread_mutex_lock(&ma);
	{
		la.push_back(a);
		if (req_a) {
			req_a = false;
			pthread_mutex_unlock(&req_am);
		}
	}
	pthread_mutex_unlock(&ma);

	for (int i = 0; i < liter; i++) {
		pthread_t tthread_aP1;
		pthread_t tthread_aP2;

		bool tryAgain = true;
		while (tryAgain) {
			pthread_mutex_lock(&my);
			{
				if (ly.empty()) {
					req_y = true;
				} else {
					*_y = ly.front();
					ly.pop_front();
					tryAgain = false;
				}
			}
			pthread_mutex_unlock(&my);
			if (tryAgain) pthread_mutex_lock(&req_ym);
		}

		pthread_create(&tthread_aP1, 0, thread_aP1, 0);
		pthread_create(&tthread_aP2, 0, thread_aP2, 0);

		pthread_join(tthread_aP2, 0);
		tmp = pow2k3 * *aP2;
		pthread_join(tthread_aP1, 0);
		a = a * *aP1 - tmp;
		pthread_mutex_lock(&ma);
		{
			la.push_back(a);
			if (req_a) {
				req_a = false;
				pthread_mutex_unlock(&req_am);
			}
		}
		pthread_mutex_unlock(&ma);
		pow2k3 *= 4;
	}

	delete aP1;
	delete aP2;
	delete _y;

	return 0;
}

void * thread_pi (void *) {
	mpf_class pi;
	for (int i = 0; i < liter; i++) {
		bool tryAgain = true;
		while (tryAgain) {
			pthread_mutex_lock(&ma);
			{
				if (la.empty()) {
					req_a = true;
				} else {
					pi = la.front();
					la.pop_front();
					tryAgain = false;
				}
			}
			pthread_mutex_unlock(&ma);
			if (tryAgain) pthread_mutex_lock(&req_am);
		}
		std::cout << "Iteracao " << i+1 << " calculada\n" ;
		pi = 1/pi;
		lpi.push_back(pi);
	}
}

void borwein1985() {
	pthread_mutex_init(&ma, 0);
	pthread_mutex_init(&my, 0);
	pthread_mutex_init(&req_am, 0);
	pthread_mutex_init(&req_ym, 0);
	pthread_mutex_lock(&req_am);
	pthread_mutex_lock(&req_ym);
	ly.clear();
	lpi.clear();
	la.clear();
	req_y = false;
	req_a = false;


	pthread_t tthread_a;
	pthread_t tthread_y;
	pthread_t tthread_pi;

	pthread_create(&tthread_a, 0, thread_a, 0);
	pthread_create(&tthread_y, 0, thread_y, 0);
	pthread_create(&tthread_pi, 0, thread_pi, 0);

	pthread_join(tthread_pi, 0);

	{
		int it = 1;
		std::list<mpf_class>::iterator itA, itB;
		itA = lpi.begin();
		itB = lpi.end();
		for(;itA!=itB;itA++) {
			std::stringstream ss;
			ss << "BMT_Iteracao_" << it++ << ".txt";
			std::fstream file;
			file.open(ss.str().c_str(), std::fstream::out);
			file.setf(std::ios::fixed, std::ios::floatfield);
			file.precision(10000000);
			file << (*itA) << std::endl;
		}
	}

	std::cout << "PI calculado com sucesso\n";
}


int main(int argc, char *argv[]) {
	mpf_set_default_prec((long int)(3.32192809488736234787 * 10000100.0));

	std::cout << "Bem vindo ao programa que calcula o PI!\n";
	std::cout << "10 milhoes de casas do pi serao calculadas!\n";
	std::cout << "Arquivos de prefixo \"BMT_\" serao criados\n";
	std::cout << "Todos os arquivos serao criados apenas ao\n\tfinal da execucao do programa!\n";

	// std::cout.setf(std::ios::fixed, std::ios::floatfield);
	// std::cout.precision(10000000);

	borwein1985();
	return 0;
}
